#include "cvm.hpp"
#include <string>
#include <iostream>
#include <cmath>

std::string Cvm::read_str() {
    int str_long;
    file.read(reinterpret_cast<char*>(&str_long), sizeof(str_long));

    std::vector<char> buffer(str_long);
    file.read(buffer.data(), str_long);

    std::string target(buffer.data(), buffer.size());
    return target;
}

Cvm::Block Cvm::read_block() {
    Block target;
    file.read(reinterpret_cast<char*>(&(target.type)), sizeof(target.type));
    if(target.type == AS_NUM) {
        file.read(reinterpret_cast<char*>(&(target.value.num)), sizeof(target.value.num));
    } else if(target.type == AS_DIRECT) {
        file.read(reinterpret_cast<char*>(&(target.value.direct)), sizeof(target.value.direct));
    } else {
        std::string* str = new std::string(read_str());
        target.value.str = (char*)str -> c_str();
    }

    return target;
}

void Cvm::load_file(std::string filename) {
    Level root;
    fn_stack.push_back(root);

    file.open(filename, std::ios::binary);
    file.read(reinterpret_cast<char*>(&code_long), sizeof(code_long));
    file.seekg(code_long);
    int label_num;
    file.read(reinterpret_cast<char*>(&label_num), sizeof(label_num));

    for(int i = 0; i < label_num; i ++) {
        std::string label_name = read_str();
        long label_addr;
        file.read(reinterpret_cast<char*>(&label_addr), sizeof(label_addr));
        label_msg[label_name] = label_addr;
    }

    file.seekg(sizeof(long), std::ios::beg);
}

Cvm::Value& Cvm::get_var_value(std::string var_name) {
    for(int i = fn_stack.size() - 1; i >= 0; i --) {
        if(fn_stack[i].vars.count(var_name)) {
            return fn_stack[i].vars[var_name];
        }
    }
}

void Cvm::set_var_value(std::string var_name, Value& value) {
    for(int i = fn_stack.size() - 1; i >= 0; i --) {
        if(fn_stack[i].vars.count(var_name)) {
            fn_stack[i].vars[var_name] = value;
            return;
        }
    }

    fn_stack.back().vars[var_name] = value;
}

void Cvm::execute() {
    while(file.tellg() < code_long) {
        Block block = read_block();
        switch(block.value.direct) {
            case DIRECT_LOAD: {
                Block block = read_block();
                Value value;
                if(block.type == AS_NUM) {
                    value.type = VALUE_NUM;
                    value.value.num = block.value.num;
                } else if(block.type == AS_VAR) {
                    std::string name(block.value.str);
                    value = get_var_value(name);
                } else {
                    value.type = VALUE_STR;
                    value.value.str = block.value.str;
                }
                stack.push_back(value);
                break;
            }

            case DIRECT_STORE: {
                Block block = read_block();
                std::string name(block.value.str);
                set_var_value(name, stack.back());

                stack.pop_back();
                break;
            }

            case DIRECT_CALC: {
                Block block = read_block();
                std::string op(block.value.str);

                Value& num1 = stack.back();
                if(op == "!") {
                    num1.value.num = ! num1.value.num;
                    continue;
                }

                Value& num2 = stack[stack.size() - 2];
                if(op == "+") {
                    num2.value.num = num2.value.num + num1.value.num;
                } else if(op == "-") {
                    num2.value.num = num2.value.num - num1.value.num;
                } else if(op == "*") {
                    num2.value.num = num2.value.num * num1.value.num;
                } else if(op == "/") {
                    num2.value.num = num2.value.num / num1.value.num;
                } else if(op == "%") {
                    num2.value.num = fmod(num2.value.num, num1.value.num);
                } else if(op == ">") {
                    num2.value.num = num2.value.num > num1.value.num;
                } else if(op == ">=") {
                    num2.value.num = num2.value.num >= num1.value.num;
                } else if(op == "<") {
                    num2.value.num = num2.value.num < num1.value.num;
                } else if(op == "<=") {
                    num2.value.num = num2.value.num <= num1.value.num;
                } else if(op == "==") {
                    num2.value.num = num2.value.num == num1.value.num;
                } else if(op == "!=") {
                    num2.value.num = num2.value.num != num1.value.num;
                } else if(op == "&&") {
                    num2.value.num = num2.value.num && num1.value.num;
                } else if(op == "||") {
                    num2.value.num = num2.value.num || num1.value.num;
                }

                stack.pop_back();
                break;
            }

            case DIRECT_JMP: {
                Block block = read_block();
                std::string label(block.value.str);
                file.seekg(label_msg[label]);
                break;
            }

            case DIRECT_JC: {
                Block block = read_block();
                std::string label(block.value.str);
                if(stack.back().value.num) {
                    file.seekg(label_msg[label]);
                }
                stack.pop_back();
                break;
            }

            case DIRECT_CALL: {
                Block block = read_block();
                std::string label(block.value.str);

                Level level;
                level.vars = *(std::map<std::string, Value>*)stack.back().value.table;
                level.return_addr = file.tellg();
                stack.pop_back();

                file.seekg(label_msg[label]);
                fn_stack.push_back(level);
                break;
            }

            case DIRECT_RET: {
                file.seekg(fn_stack.back().return_addr);
                fn_stack.pop_back();

                break;
            }

            case DIRECT_TABLE: {
                Block block = read_block();
                std::string option(block.value.str);
                if(option == "new") {
                    Value value;
                    value.type = VALUE_TABLE;
                    value.value.table = (void*)(new std::map<std::string, Value>());
                    stack.push_back(value);
                } else if(option == "assign") {
                    std::string index;
                    if(stack[stack.size() - 2].type == VALUE_NUM) {
                        index = std::to_string((int)(stack[stack.size() - 2].value.num));
                    } else {
                        index = stack[stack.size() - 2].value.str;
                    }

                    std::map<std::string, Value>* table = (std::map<std::string, Value>*)stack[stack.size() - 3].value.table;
                    (*table)[index] = stack.back();

                    stack.pop_back();
                    stack.pop_back();
                } else if(option == "get") {
                    std::string index;
                    if(stack.back().type == VALUE_NUM) {
                        index = std::to_string((int)(stack.back().value.num));
                    } else {
                        index = stack.back().value.str;
                    }

                    std::map<std::string, Value>* table = (std::map<std::string, Value>*)stack[stack.size() - 2].value.table;
                    stack.back() = (*table)[index];
                }
                break;
            }
            
            case DIRECT_FN: {
                Block block = read_block();
                std::string option(block.value.str);
                if(option == "print") {
                    Value& value = stack.back();
                    if(value.type == VALUE_NUM) {
                        printf("%g", value.value.num);
                    } else if(value.type == VALUE_STR) {
                        printf("%s", value.value.str);
                    }

                    stack.pop_back();
                } else if(option == "input") {
                    std::string* string = new std::string;
                    std::cin >> *string;

                    Value value;
                    value.type = VALUE_STR;
                    value.value.str = (char*)(string -> c_str());
                    stack.push_back(value);
                } else if(option == "exit") {
                    exit(0);
                }
                break;
            }
        }
    }
}

#ifdef DEBUG
int main() {
    Cvm cvm;
    cvm.load_file("test.bin");
    cvm.execute();
}
#endif